﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Security;
using Microsoft.Xrm.Client;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using Microsoft.Xrm.Client.Services;
using System.ServiceModel;
using System.Web.Configuration;
using System.Configuration;

namespace Applications.Framework.Web.Providers
{
    public class CrmRoleProvider : RoleProvider
    {

        #region ctor

        public CrmRoleProvider()
            : base()
        {
            InitializeCrmConnection("");
        }


        ProviderSettings Settings = null;
        private void InitializeProviderSettings()
        {
            RoleManagerSection section = (RoleManagerSection)System.Web.Configuration.WebConfigurationManager.GetSection("system.web/roleManager");
            Settings = section.Providers["CrmRoleProvider"];
        }

        private Microsoft.Xrm.Client.CrmConnection Connection = null;
        private void InitializeCrmConnection(string CrmConnectionString)
        {
            try
            {
                InitializeProviderSettings();

                if (string.IsNullOrEmpty(CrmConnectionString))
                {
                    string ConName = Settings.Parameters["connectionStringName"];
                    CrmConnectionString = System.Web.Configuration.WebConfigurationManager.ConnectionStrings[ConName].ConnectionString;
                }
                Connection = Microsoft.Xrm.Client.CrmConnection.Parse(CrmConnectionString);
                Connection.ServiceConfigurationInstanceMode = ServiceConfigurationInstanceMode.PerInstance;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        #endregion

        #region Public properties

        public string EntityName
        {
            get { return Settings.Parameters["entityName"]; }
        }

        public string UserEntityName
        {
            get { return Settings.Parameters["userEntity"]; }
        }

        public string RoleNameField
        {
            get { return Settings.Parameters["roleNameField"]; }
        }

        public string ApplicationNameField
        {
            get { return Settings.Parameters["applicationNameField"]; }
        }

        public string UserNameField
        {
            get { return Settings.Parameters["userNameField"]; }
        }

        public string UserRelationshipName
        {
            get { return Settings.Parameters["userRelationshipName"]; }
        }



        #endregion



        #region Role Provider implementation

        public override void AddUsersToRoles(string[] usernames, string[] roleNames)
        {
            FilterExpression filter = new FilterExpression(LogicalOperator.And);
            filter.AddCondition(new ConditionExpression(UserNameField, ConditionOperator.In, usernames));
            filter.AddCondition(new ConditionExpression(ApplicationNameField, ConditionOperator.Equal, ApplicationName));
            EntityCollection users = CrmCommon.RetrieveByFilter(UserEntityName, filter, Connection);
            filter = new FilterExpression(LogicalOperator.And);
            filter.AddCondition(new ConditionExpression(RoleNameField, ConditionOperator.In, roleNames));
            filter.AddCondition(new ConditionExpression(ApplicationNameField, ConditionOperator.Equal, ApplicationName));
            EntityCollection Roles = CrmCommon.RetrieveByFilter(EntityName, filter, Connection);
            foreach (Entity user in users.Entities)
            {
                CrmCommon.AddRelated(user, Roles, UserRelationshipName, Connection);
            }
        }

        public override string ApplicationName
        {
            get
            {
                return Settings.Parameters["applicationName"];
            }
            set
            {

            }
        }

        public override void CreateRole(string roleName)
        {
            Entity ent = new Entity(EntityName);
            ent.SetAttributeValue<string>(ApplicationNameField, ApplicationName);
            ent.SetAttributeValue(RoleNameField, roleName);
            CrmCommon.CreateEntity(ent, Connection);
        }

        public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
        {
            string[] Users = GetUsersInRole(roleName);
            if (Users.Length > 0)
            {
                if (throwOnPopulatedRole)
                {
                    throw new Exception(string.Format("Cannot delete the {0}. {1} Users belong to this role", roleName, Users.Length));
                }
                else
                {
                    return false;
                }
            }
            else
            {
                FilterExpression filter = new FilterExpression(LogicalOperator.And);
                filter.AddCondition(new ConditionExpression(ApplicationNameField, ConditionOperator.Equal, ApplicationName));
                filter.AddCondition(new ConditionExpression(RoleNameField, ConditionOperator.Equal, roleName));
                EntityCollection col = CrmCommon.RetrieveByFilter(EntityName, filter, Connection);
                if (col.Entities.Count == 1)
                {
                    CrmCommon.DeleteEntity(col.Entities.First(), Connection);
                    return true;
                }
                else
                {
                    return false;
                }
            }
        }

        public override string[] FindUsersInRole(string roleName, string usernameToMatch)
        {
            using (CrmOrganizationServiceContext service = new CrmOrganizationServiceContext(Connection))
            {
                IQueryable<Entity> query = from users in service.CreateQuery(UserEntityName)
                                           join related in service.CreateQuery(UserRelationshipName) on users[UserEntityName + "id"] equals related[UserEntityName + "id"]
                                           join role in service.CreateQuery(EntityName) on related[EntityName + "id"] equals role[EntityName + "id"]
                                           where role[RoleNameField] == roleName && role[ApplicationNameField] == ApplicationName && ((string)users[UserNameField]).StartsWith(usernameToMatch)
                                           select users;

                EntityCollection col = new EntityCollection(query.ToList());
                return col.Entities.Select(x => x.GetAttributeValue<string>(UserNameField)).ToArray();
            }
        }

        public override string[] GetAllRoles()
        {
            FilterExpression filter = new FilterExpression(LogicalOperator.And);
            filter.AddCondition(new ConditionExpression(ApplicationNameField, ConditionOperator.Equal, ApplicationName));
            EntityCollection col = CrmCommon.RetrieveByFilter(EntityName, filter, Connection);
            return col.Entities.Select(x => x.GetAttributeValue<string>(RoleNameField)).ToArray();
        }

        public override string[] GetRolesForUser(string username)
        {
            using (CrmOrganizationServiceContext service = new CrmOrganizationServiceContext(Connection))
            {
                IQueryable<Entity> query = from role in service.CreateQuery(EntityName)
                                           join related in service.CreateQuery(UserRelationshipName) on role[EntityName + "id"] equals related[EntityName + "id"]
                                           join user in service.CreateQuery(UserEntityName) on related[UserEntityName + "id"] equals user[UserEntityName + "id"]
                                           where user[UserNameField] == username && role[ApplicationNameField] == ApplicationName
                                           select role;

                EntityCollection col = new EntityCollection(query.ToList());
                return col.Entities.Select(x => x.GetAttributeValue<string>(RoleNameField)).ToArray();
            }
        }

        public override string[] GetUsersInRole(string roleName)
        {
            using (CrmOrganizationServiceContext service = new CrmOrganizationServiceContext(Connection))
            {
                IQueryable<Entity> query = from users in service.CreateQuery(UserEntityName)
                                           join related in service.CreateQuery(UserRelationshipName) on users[UserEntityName + "id"] equals related[UserEntityName + "id"]
                                           join role in service.CreateQuery(EntityName) on related[EntityName + "id"] equals role[EntityName + "id"]
                                           where role[RoleNameField] == roleName && role[ApplicationNameField] == ApplicationName
                                           select users;

                EntityCollection col = new EntityCollection(query.ToList());
                return col.Entities.Select(x => x.GetAttributeValue<string>(UserNameField)).ToArray();
            }
        }

        public override bool IsUserInRole(string username, string roleName)
        {
            using (CrmOrganizationServiceContext service = new CrmOrganizationServiceContext(Connection))
            {
                IQueryable<Entity> query = from users in service.CreateQuery(UserEntityName)
                                           join related in service.CreateQuery(UserRelationshipName) on users[UserEntityName + "id"] equals related[UserEntityName + "id"]
                                           join role in service.CreateQuery(EntityName) on related[EntityName + "id"] equals role[EntityName + "id"]
                                           where role[RoleNameField] == roleName && role[ApplicationNameField] == ApplicationName && users[UserNameField] == username
                                           select users;

                return query.Count() == 1;
            }
        }

        public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
        {
            FilterExpression filter = new FilterExpression(LogicalOperator.And);
            filter.AddCondition(new ConditionExpression(UserNameField, ConditionOperator.In, usernames));
            filter.AddCondition(new ConditionExpression(ApplicationNameField, ConditionOperator.Equal, ApplicationName));
            EntityCollection users = CrmCommon.RetrieveByFilter(UserEntityName, filter, Connection);
            filter = new FilterExpression(LogicalOperator.And);
            filter.AddCondition(new ConditionExpression(RoleNameField, ConditionOperator.In, roleNames));
            filter.AddCondition(new ConditionExpression(ApplicationNameField, ConditionOperator.Equal, ApplicationName));
            EntityCollection Roles = CrmCommon.RetrieveByFilter(EntityName, filter, Connection);
            foreach (Entity user in users.Entities)
            {
                CrmCommon.RemoveRelated(user, Roles, UserRelationshipName, Connection);
            }

        }

        public override bool RoleExists(string roleName)
        {
            FilterExpression filter = new FilterExpression(LogicalOperator.And);
            filter.AddCondition(new ConditionExpression(RoleNameField, ConditionOperator.Equal, roleName));
            filter.AddCondition(new ConditionExpression(ApplicationNameField, ConditionOperator.Equal, ApplicationName));
            EntityCollection col = CrmCommon.RetrieveByFilter(EntityName, filter, Connection);
            return col.Entities.Count == 1;
        }

        #endregion
    }
}
